/*
 * Sphere.cpp
 *
 *  Created on: 28/03/2013
 *      Author: gonzalo
 */

#include "Sphere.h"

#include <vector>

void Sphere::create_vertex_buffer(GLfloat radius, GLuint loops, GLuint segmentsPerLoop) {
	std::vector<GLfloat> vertex_buffer_vector;
	float theta = 0, cosTheta = std::cos(theta), sinTheta = std::sin(theta);
	float phi, cosPhi, sinPhi;

	for (GLuint loopSegmentNumber = 0; loopSegmentNumber < segmentsPerLoop; ++loopSegmentNumber) {
		phi = loopSegmentNumber * 2 * PI / segmentsPerLoop;
		cosPhi = std::cos(phi);
		sinPhi = std::sin(phi);
		vertex_buffer_vector.push_back(radius * cosPhi * sinTheta); // Vx
		vertex_buffer_vector.push_back(radius * sinPhi * sinTheta); // Vy
		vertex_buffer_vector.push_back(radius * cosTheta); // Vz
	}

	for (GLuint loopNumber = 0; loopNumber <= loops; ++loopNumber) {
		for (GLuint loopSegmentNumber = 0; loopSegmentNumber < segmentsPerLoop; ++loopSegmentNumber) {
			theta = (loopNumber * PI / loops) + ((PI * loopSegmentNumber) / (segmentsPerLoop * loops));
			if (loopNumber == loops) {
				theta = PI;
			}
			phi = loopSegmentNumber * 2 * PI / segmentsPerLoop;
			cosTheta = std::cos(theta);
			sinTheta = std::sin(theta);
			cosPhi = std::cos(phi);
			sinPhi = std::sin(phi);

			vertex_buffer_vector.push_back(radius * cosPhi * sinTheta);
			vertex_buffer_vector.push_back(radius * sinPhi * sinTheta);
			vertex_buffer_vector.push_back(radius * cosTheta);

		}
	}

	vertex_buffer = new GLfloat[vertex_buffer_vector.size()];

	std::vector<GLfloat>::iterator vertex_it;
	unsigned int v = 0;
	for (vertex_it = vertex_buffer_vector.begin(); vertex_it != vertex_buffer_vector.end(); vertex_it++) {
		vertex_buffer[v] = *vertex_it;
		v++;
	}

	vertex_buffer_size = vertex_buffer_vector.size();
}

void Sphere::create_index_buffer(GLuint loops, GLuint segmentsPerLoop) {
	std::vector<GLuint> index_buffer_vector;
	for (unsigned int loopSegmentNumber = 0; loopSegmentNumber < segmentsPerLoop; ++loopSegmentNumber) {
		index_buffer_vector.push_back(loopSegmentNumber);
		index_buffer_vector.push_back(segmentsPerLoop + loopSegmentNumber);
	}
	for (unsigned int loopNumber = 0; loopNumber < loops; ++loopNumber) {
		for (unsigned int loopSegmentNumber = 0; loopSegmentNumber < segmentsPerLoop; ++loopSegmentNumber) {
			index_buffer_vector.push_back(((loopNumber + 1) * segmentsPerLoop) + loopSegmentNumber);
			index_buffer_vector.push_back(((loopNumber + 2) * segmentsPerLoop) + loopSegmentNumber);
		}
	}

	index_buffer = new GLuint[index_buffer_vector.size()];

	std::vector<GLuint>::iterator index_it;
	unsigned int i = 0;
	for (index_it = index_buffer_vector.begin(); index_it != index_buffer_vector.end(); index_it++) {
		index_buffer[i] = *index_it;
		i++;
	}

	index_buffer_size = index_buffer_vector.size();
}

void Sphere::create_normal_buffer(GLfloat radius) {
	normal_buffer = new GLfloat[3 * index_buffer_size];

	for (uint i = 0; i < index_buffer_size; i++) {
		normal_buffer[3 * i] = vertex_buffer[3 * index_buffer[i]] / radius;
		normal_buffer[3 * i + 1] = vertex_buffer[3 * index_buffer[i] + 1] / radius;
		normal_buffer[3 * i + 2] = vertex_buffer[3 * index_buffer[i] + 2] / radius;
	}
}

Sphere::Sphere(GLuint  program, GLfloat radius, GLuint loops, GLuint segmentsPerLoop) :
		Figure(program) {
	if (vertex_buffer == NULL) {
		this->create_vertex_buffer(radius, loops, segmentsPerLoop);
		this->create_index_buffer(loops, segmentsPerLoop);
		this->create_normal_buffer(radius);
	}
}

Sphere::~Sphere() {
	if (vertex_buffer != NULL) {
		delete[] vertex_buffer;
		delete[] normal_buffer;
		delete[] index_buffer;
	}
}

void Sphere::draw(GLuint program, glm::mat4 & world_matrix) {
	glm::mat4 matriz = world_matrix * this->model_matrix;

	calculate_and_bind_normal_matrix(program,matriz);
	bind_model_matrix(program,matriz);

	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_NORMAL_ARRAY);

	glVertexPointer(3, GL_FLOAT, 0, vertex_buffer);
	glNormalPointer(GL_FLOAT, 0, normal_buffer);

	glDrawElements(GL_TRIANGLE_STRIP, index_buffer_size, GL_UNSIGNED_INT, index_buffer);

	if (this->show_normals) {
		draw_normals();
	}

	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_NORMAL_ARRAY);
}
